﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using vm.Aspects.Threading;

namespace vm.Aspects.Diagnostics.DumpImplementation
{
    /// <summary>
    /// Primary responsibility of the class is to retrieve the DumpAttribute instance associated with a property to be dumped from the property's 
    /// class or associated metadata type (buddy). For improved performance the class encapsulates a cache of PropertyInfo describing the property
    /// and the associated <see cref="DumpAttribute"/>.
    /// </summary>
    static class PropertyDumpResolver
    {
        static Dictionary<Tuple<PropertyInfo, Type>, DumpAttribute> _propertyDumpMap = new Dictionary<Tuple<PropertyInfo, Type>, DumpAttribute>();
        static ReaderWriterLockSlim _lockPropertyDumpMap = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

        /// <summary>
        /// Gets the synchronization object of the cache/dictionary of property info-dump attributes.
        /// </summary>
        static ReaderWriterLockSlim SyncPropertiesDumpData
        {
            get { return _lockPropertyDumpMap; }
        }

        /// <summary>
        /// Gets the cache/dictionary of property info-dump attributes.
        /// </summary>
        static Dictionary<Tuple<PropertyInfo, Type>, DumpAttribute> PropertiesDumpData
        {
            get { return _propertyDumpMap; }
        }

        /// <summary>
        /// Gets the dump attribute applied to a property.
        /// </summary>
        /// <param name="pi">The property info.</param>
        /// <param name="metadata">Type of the metadata.</param>
        /// <returns></returns>
        public static DumpAttribute GetPropertyDumpAttribute(
            PropertyInfo pi,
            Type metadata = null)
        {
            if (pi == null)
                throw new ArgumentNullException("pi");

            var lookup = Tuple.Create(pi, metadata);
            DumpAttribute dumpAttribute;

            // if we have the dump attribute in the cache - return it
            using (SyncPropertiesDumpData.ReaderSync())
                if (PropertiesDumpData.TryGetValue(lookup, out dumpAttribute))
                    return dumpAttribute;

            // is there an attribute on the corresponding property in the buddy
            if (metadata != null  &&  pi.GetIndexParameters().Length == 0)
            {
                PropertyInfo piMeta = null;

                foreach (var p in metadata.GetProperties()
                                          .Where(p => p.Name == pi.Name))
                    if (piMeta == null)
                        piMeta = p;
                    else
                        // hidden property(?) - take the property from the most derived class:
                        if (piMeta.DeclaringType.IsAssignableFrom(p.DeclaringType))
                        piMeta = p;

                if (piMeta!=null)
                    dumpAttribute = piMeta.GetCustomAttribute<DumpAttribute>();
            }

            if (dumpAttribute == null)
            {
                // get the attribute directly from the property's type
                dumpAttribute = pi.GetCustomAttribute<DumpAttribute>();

                // if still no dump attribute - assume the default dump attribute
                if (dumpAttribute == null)
                    dumpAttribute = DumpAttribute.Default;
            }

            // put the property info and the attribute in the cache
            using (SyncPropertiesDumpData.WriterSync())
                PropertiesDumpData[lookup] = dumpAttribute;

            // return the dump attribute
            return dumpAttribute;
        }

        public static bool PropertyHasNonDefaultDumpAttribute(PropertyInfo propertyInfo)
        {
            using (SyncPropertiesDumpData.ReaderSync())
                return _propertyDumpMap.Any(
                            kv => kv.Key.Item1.Name == propertyInfo.Name  &&
                                  kv.Key.Item1.DeclaringType.IsAssignableFrom(propertyInfo.DeclaringType)   &&
                                  !kv.Value.IsDefaultAttribute());
        }
    }
}
